Making a Basic App

Decomposing native plot function

Using the output of the standard plotting functions we craft a dynamic version of it. For example

model = RevolutionPlot3D[{Sin[t] + Sin[5 t] /10, Cos[t] + Cos[5 t] /10}, {t, 0, Pi}, RegionFunction -> (Sin[5 (#4 + #5)] > 0 &), Mesh -> None, BoundaryStyle -> Black, PlotStyle -> Thickness[.1], MaxRecursion->1] Here we can find, that no matter how we change the parmeters of the curve our graphics is packed into a single `GraphicsComplex`

Cases[model, _GraphicsComplex, 3] // First // Short And 3 groups of polygons stored in the list of the second argument

Cases[Cases[model, _GraphicsComplex, 3][[1,2]], _Polygon, 7] // Short We can pick them and construct a dynamic version of `RevolutionPlot3D`

Making dynamic version

Now let's define vertices and indices symbols and a function to update them

normals; vertices; indices1; indices2; updateFigure[model_] := With[{ complex = Cases[model, _GraphicsComplex, 3, 1][[1]] }, With[{polygons = Cases[complex[[2]], _Polygon, 7][[All,1]]}, {indices1, indices2} = {polygons[[1]], polygons[[2]]}; (* we discarded the 3rd set of polygons, since it is small and only highlights the edges *) normals = complex[[3,2]]; vertices = complex[[1]]; ] ]; updateFigure[model]; Now let's hook up some sliders

updateModel[s_:5, a_:0.1] := RevolutionPlot3D[{Sin[t] + Sin[s t] a, Cos[t] + Cos[s t] a}, {t, 0, Pi}, RegionFunction -> (Sin[5 (#4 + #5)] > 0 &), Mesh -> None, BoundaryStyle -> Black, PlotStyle -> Thickness[.1], MaxRecursion->1] sliders = EventHandler[InputGroup[<| "s" -> InputRange[1,10,1, "Label"->"s"], "a" -> InputRange[0.1, 0.5, 0.1, 0.1, "Label"->"a"] |>], Function[values, model = updateModel @@ Values[values]; updateFigure[model]; ]]; Now our dynamic 3D scene

{ Graphics3D[GraphicsComplex[vertices // Offload, { Polygon[indices1 // Offload], Polygon[indices2 // Offload] }, VertexNormals->Offload[normals, "Static"->True]], ImageSize->Medium], sliders } // Row

Export button

Now we need to export this. If this is running on web, you might need to implement a donwload function in Javascript, since there will be no files API available. However, here we will stick to the eastiest way

export[_] := Then[SystemDialogInputAsync["FileSave", {Null, {"STL Formats" -> {"*.stl"}}}], Function[path, If[StringQ[path], Export[path, model]; Beep[]; ] ]]; EventHandler[InputButton["Export"], export]

Making an app

Taking all of this together, we can export it a standalone widget. For that we need

1. Define initialization cells

2. Put the output code of the widget to the last input cell of the notebook

3. Click `Share` and export to widget


:::tip

In this notebook first two steps are done, you only need to export it to WLJS Widget format

:::

For the output we use `.wlx` cells, since it allows more styling options using pure HTML and CSS for the final look of the app

.wlx With[{ Preview = Graphics3D[GraphicsComplex[vertices // Offload, { Polygon[indices1 // Offload], Polygon[indices2 // Offload] }, VertexNormals->Offload[normals, "Static"->True]], ImageSize->Medium], Sliders = sliders, ExportButton = EventHandler[InputButton["Export"], export] }, <div class="bg-white p-4 w-full h-full"> <div class="flex flex-row gap-x-2 justify-between"> <Preview/> <div class="flex flex-col gap-y-3 justify-between"> <Sliders/> <ExportButton/> </div> </div> </div> ]